﻿using DevExpress.ExpressApp;
using DevExpress.ExpressApp.ApplicationBuilder;
using DevExpress.ExpressApp.Security;
using DevExpress.ExpressApp.WebApi.Mvc;
using DevExpress.ExpressApp.WebApi.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.OData;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.OpenApi.Models;
using System.Globalization;
using System.Net;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Http.ProxyScripting.Generators.JQuery;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Swashbuckle;
using Volo.Abp.UI.Navigation;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Validation.Localization;
using Volo.Abp.VirtualFileSystem;
using YAKEJI.Abp.ChineseChar;
using YAKEJI.Abp.Database;
using YAKEJI.Abp.Xpo;
using YAKEJI.Abp.Xpo.DependencyInjection;
using YAKEJI.Xaf.ApplicationBuilder;
using YAKEJI.Xaf.DataDictionaries;
using YAKEJI.Xaf.Numbers;
using YAPACS.BusinessObjects.Services;
using YAPACS.Organizations;
using YAPACS.RisServer.Localization;
using YAPACS.RisServer.Menus;
using YAPACS.Workflows;

namespace YAPACS.RisServer;

[DependsOn(
    // ABP Framework packages
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule),
    typeof(AbpAutoMapperModule),
    typeof(AbpSwashbuckleModule),
    typeof(AbpAspNetCoreSerilogModule),
    typeof(AbpAspNetCoreMvcUiBasicThemeModule),

    // YAKEJI
    typeof(YAKEJIAbpChineseCharModule),
    typeof(YAKEJIAbpDatabaseModule),
    typeof(YAKEJIAbpXpoModule),
    typeof(XafNumbersXpoAbpModule),
    typeof(XafDataDictionariesXpoAbpModule),

    // Workflow
    typeof(WorkflowsAbpModule)
)]
public class RisServerAbpModule : AbpModule
{

    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();

        context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
        {
            options.AddAssemblyResource(
                typeof(RisServerResource)
            );
        });
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
        ConfigureForwarded();
        ConfigureCors(context, configuration);
        ConfigureHttpContext(context);
        ConfigureMultiTenancy();
        ConfigureUrls(configuration);
        ConfigureBundles();
        ConfigureAutoMapper(context);
        ConfigureSwagger(context.Services);
        ConfigureNavigationServices();
        ConfigureAutoApiControllers();
        ConfigureVirtualFiles(hostingEnvironment);
        ConfigureLocalization();
        ConfigureAuth(context.Services);
        ConfigureWorkflow();
        ConfigureXpo();
        ConfigureXaf(context.Services, configuration);
        ConfigureOData(context.Services);
        ConfigDynamicJavaScriptProxy(context);
    }

    private void ConfigureForwarded()
    {
        Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
            options.KnownProxies.Add(IPAddress.Parse("127.0.0.1"));
            options.KnownProxies.Add(IPAddress.Parse("::1"));
        });
    }

    private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.RemovePostFix("/"))
                            .ToArray() ?? []
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .WithHeaders("Content-Type")
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });
    }

    private void ConfigureHttpContext(ServiceConfigurationContext context)
    {
        context.Services.AddHttpClient();
    }

    private void ConfigDynamicJavaScriptProxy(ServiceConfigurationContext context)
    {
        context.Services.Configure<DynamicJavaScriptProxyOptions>(options =>
        {
            options.DisableModule("app");
        });
    }

    private void ConfigureMultiTenancy()
    {
        Configure<AbpMultiTenancyOptions>(options =>
        {
            options.IsEnabled = false;
        });
    }

    private void ConfigureUrls(IConfiguration configuration)
    {
        Configure<AppUrlOptions>(options =>
        {
            options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
        });
    }

    private void ConfigureBundles()
    {
        Configure<AbpBundlingOptions>(options =>
        {
            options.Mode = BundlingMode.None;

            options.StyleBundles.Configure(
                BasicThemeBundles.Styles.Global,
                bundle =>
                {
                    bundle.AddFiles(
                        "/js/jquery-ui/jquery-ui.min.css",
                        "/js/devextreme/css/dx.common.css",
                        "/js/devextreme/css/dx.fluent.blue.light.css",
                        "/js/amis/sdk.css",
                        "/js/amis/helper.css",
                        "/js/amis/iconfont.css",
                        "/js/load-indicator/load-indicator.css",
                        "/js/data-grid/yakeji-data-grid.css",
                        "/js/splitter/yakeji-splitter.css",
                        "/global-styles.css"
                    );
                }
            );
            options.ScriptBundles.Configure(
                BasicThemeBundles.Scripts.Global,
                bundle =>
                {
                    bundle.AddFiles(
                        "/js/jquery-ui/jquery-ui.min.js",
                        "/js/devextreme/js/dx.all.js",
                        "/js/devextreme/js/localization/dx.messages.zh.js",
                        "/js/common.js",
                        "/js/amis/sdk.js",
                        "/js/expr-eval/expr-eval.js",
                        "/js/load-indicator/load-indicator.js",
                        "/js/form-popup/yakeji-form-popup.js",
                        "/js/jquery-amis/yakeji-jquery-amis.js",
                        "/js/invoke-action/yakeji-invoke-action.js",
                        "/js/advanced-search/yakeji-advanced-search.js",
                        "/js/advanced-search/yakeji-advanced-search-with-collapse.js",
                        "/js/quick-filter/yakeji-quick-filter.js",
                        "/js/data-grid/yakeji-data-grid.js",
                        "/js/exam-grid/yakeji-exam-grid.js",
                        "/js/splitter/yakeji-splitter.js",
                        "/js/amis-form/yakeji-amis-form.js",
                        "/ejs/ejs.js");
                }
            );
        });
    }

    private void ConfigureLocalization()
    {
        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Add<RisServerResource>("en")
                .AddBaseTypes(typeof(AbpValidationResource))
                .AddVirtualJson("/Localization/RisServer");

            options.DefaultResourceType = typeof(RisServerResource);

            options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文"));
        });

        Configure<AbpExceptionLocalizationOptions>(options =>
        {
            options.MapCodeNamespace("RisServer", typeof(RisServerResource));
        });
    }

    private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment)
    {
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.AddEmbedded<RisServerAbpModule>();
            if (hostingEnvironment.IsDevelopment())
            {
                /* Using physical files in development, so we don't need to recompile on changes */
                options.FileSets.ReplaceEmbeddedByPhysical<RisServerAbpModule>(hostingEnvironment.ContentRootPath);
            }
        });
    }

    private void ConfigureNavigationServices()
    {
        Configure<AbpNavigationOptions>(options =>
        {
            options.MenuContributors.Add(new RisServerMenuContributor());
        });
    }

    private void ConfigureAutoApiControllers()
    {
        Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            options.ConventionalControllers.Create(typeof(RisServerAbpModule).Assembly);
        });
    }

    private void ConfigureSwagger(IServiceCollection services)
    {
        services.AddAbpSwaggerGen(
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "RisServer API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
            }
        );
    }

    private void ConfigureAutoMapper(ServiceConfigurationContext context)
    {
        context.Services.AddAutoMapperObjectMapper<RisServerAbpModule>();
        Configure<AbpAutoMapperOptions>(options =>
        {
            /* Uncomment `validate: true` if you want to enable the Configuration Validation feature.
             * See AutoMapper's documentation to learn what it is:
             * https://docs.automapper.org/en/stable/Configuration-validation.html
             */
            options.AddMaps<RisServerAbpModule>(/* validate: true */);

        });
    }

    private void ConfigureAuth(IServiceCollection services)
    {
        services
            .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.LoginPath = "/Account/Login";
            });

        services.AddAuthorization();
    }

    private void ConfigureWorkflow()
    {
        Configure<WorkflowOptions>(options =>
        {
            options.WorkflowAssemblies.Add(typeof(RisServerAbpModule).Assembly);
        });
    }

    private void ConfigureXpo()
    {
        Configure<AbpXpoContextOptions>(options =>
        {
            options.Configure(configurationContext =>
            {
                configurationContext.UseXpo();
            });
        });
    }

    private static void ConfigureXaf(IServiceCollection services, IConfiguration configuration)
    {
        services.AddXafWebApi(builder =>
        {
            builder.AddXpoServices();

            builder.ConfigureOptions(options =>
            {
                //options.BusinessObject<ApplicationUser>();
                //options.BusinessObject<UserGroup>();
                //options.BusinessObject<Department>();
                //options.BusinessObject<Institution>();
                //options.BusinessObject<DepartmentDeviceType>();
                //options.BusinessObject<DeviceType>();
                //options.BusinessObject<DataDictItem>();
                //options.BusinessObject<DataDictionary>();
                //options.BusinessObject<ApplyDepartment>();
                //options.BusinessObject<ApplyDoctor>();
                //options.BusinessObject<PatientFrom>();
                //options.BusinessObject<ExamBodyParty>();
                //options.BusinessObject<ExamMethod>();
                //options.BusinessObject<PatientFeature>();
                //options.BusinessObject<AppointmentPlan>();
                //options.BusinessObject<AppointmentTimeInterval>();
                //options.BusinessObject<ExamItem>();
                //options.BusinessObject<Exam>();
                //options.BusinessObject<Patient>();
            });

            builder.Modules.Add<RisServerModule>();

            builder.ObjectSpaceProviders
                .AddCustomXpo((serviceProvider, options) =>
                {
                    options.ConnectionString = configuration.GetConnectionString("Default");
                    options.EnablePoolingInConnectionString = false;
                    options.UseSharedDataStoreProvider = false;
                    options.ThreadSafe = false;
                })
                .AddNonPersistent();

            builder.Security.UseIntegratedMode(options =>
            {
                options.Lockout.Enabled = false;
                options.RoleType = typeof(ApplicationRole);
                options.UserType = typeof(ApplicationUser);
                options.UseXpoPermissionsCaching();
                options.Events.OnSecurityStrategyCreated += securityStrategy =>
                {
                    ((SecurityStrategy)securityStrategy).PermissionsReloadMode = PermissionsReloadMode.CacheOnFirstAccess;
                };
                options.Events.OnCreateCustomSecurityFunctionPatcher = securityFunctionPatcher =>
                {
                    securityFunctionPatcher.CustomSecurityFunctionPatcher = ActivatorUtilities.CreateInstance<BusinessObjectSecurityFunctionPatcherService>(securityFunctionPatcher.ServiceProvider);
                };
            })
            .AddPasswordAuthentication(options =>
            {
                options.IsSupportChangePassword = true;
            });

            builder.AddBuildStep(application =>
            {
                application.ApplicationName = "RisServer";
                application.CheckCompatibilityType = CheckCompatibilityType.DatabaseSchema;
#if DEBUG
                if (System.Diagnostics.Debugger.IsAttached && application.CheckCompatibilityType == CheckCompatibilityType.DatabaseSchema)
                {
                    application.DatabaseUpdateMode = DatabaseUpdateMode.UpdateDatabaseAlways;
                    application.DatabaseVersionMismatch += (s, e) =>
                    {
                        e.Updater.Update();
                        e.Handled = true;
                    };
                }
#endif
            });

        }, configuration);

        services.TryAddScoped(s =>
        {
            var requiredService = s.GetRequiredService<ICriteriaOperatorPatcherEvents>();
            var createCustomSecurityFunctionPatcherContext = new CreateCustomSecurityFunctionPatcherContext(s, s.GetService<ISecurityStrategyBase>());
            requiredService.CreateCustomSecurityFunctionPatcher(createCustomSecurityFunctionPatcherContext);
            return createCustomSecurityFunctionPatcherContext.CustomSecurityFunctionPatcher ?? s.GetRequiredService<CriteriaOperatorPatcherBase>();
        });
    }

    private static void ConfigureOData(IServiceCollection services)
    {
        services.AddTransient(typeof(DataController<,>));
        services.AddTransient(typeof(XafDelta<>));

        services
            .AddControllers()
            .AddOData((options, serviceProvider) =>
            {
                options
                    .AddRouteComponents("api/odata", new EdmModelBuilder(serviceProvider).GetEdmModel())
                    .EnableQueryFeatures(100);
            });

        //services.Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(o =>
        //{
        //    o.JsonSerializerOptions.PropertyNamingPolicy = null;
        //});
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseForwardedHeaders();
        app.UseAbpRequestLocalization();

        if (!env.IsDevelopment())
        {
            app.UseErrorPage();
        }

        app.UseAbpRequestLocalization(options =>
        {
            var supportedCultures = new[]
            {
                new CultureInfo("zh-Hans")
            };

            options.DefaultRequestCulture = new RequestCulture("zh-Hans");
            options.SupportedCultures = supportedCultures;
            options.SupportedUICultures = supportedCultures;
            options.RequestCultureProviders =
            [
                new QueryStringRequestCultureProvider(),
                new CookieRequestCultureProvider()
            ];
        });

        app.UseCorrelationId();

        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(
                Path.Combine(Directory.GetCurrentDirectory(), "./pages/")
            )
        });

        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthentication();

        app.UseCors();

        app.UseUnitOfWork();
        app.UseAuthorization();

        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "RisServer API");
        });

        app.Use(async (context, next) =>
        {
            var stationCode = context.Request.Query["__station_code"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(stationCode))
            {
                context.Response.Cookies.Append("StationCode", stationCode.ToUpperInvariant());
            }

            var groupCode = context.Request.Query["__group_code"].FirstOrDefault();
            if (!string.IsNullOrWhiteSpace(groupCode))
            {
                context.Response.Cookies.Append("GroupCode", groupCode.ToUpperInvariant());
            }

            await next();
        });

        app.UseAbpSerilogEnrichers();
        app.UseConfiguredEndpoints();
    }
}
